#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Some extra-nice extra bits for json encoding and decoding.

Instead of using simplejson directly, clients should import the two
wrappers we define here:

    >>> from tack.lib.jsontools import json_dumps, json_loads

Basic encoding works just like you would expect:

    >>> json_dumps('any')
    '"any"'

    >>> a = json_dumps(range(3))
    >>> a
    '[0, 1, 2]'

    >>> b = json_loads(a)
    >>> b
    [0, 1, 2]

Types from the datetime module are also supported.  Example of
datetime:

    >>> d = datetime(2001, 6, 1, 13, 14, 15, 1500)
    >>> json_dumps(d)
    '{"__datetime__": [2001, 6, 1, 13, 14, 15, 1500]}'

    >>> json_loads(json_dumps(d))
    datetime.datetime(2001, 6, 1, 13, 14, 15, 1500)

And of a date object:

    >>> d = date(2002, 12, 3)
    >>> json_dumps(d)
    '{"__date__": [2002, 12, 3]}'

    >>> json_loads(json_dumps(d))
    datetime.date(2002, 12, 3)

And finally an example of a time object:

    >>> t = time(0, 1, 2, 3)
    >>> json_dumps(t)
    '{"__time__": [0, 1, 2, 3]}'

    >>> json_loads(json_dumps(t))
    datetime.time(0, 1, 2, 3)

Encoding a path:

    >>> t = '/path/to/a/file.txt'
    >>> e = json_dumps(t)
    >>> print e
    "\/path\/to\/a\/file.txt"


    >>> d = json_loads(e)
    >>> d
    u'/path/to/a/file.txt'

"""
from time import strftime, strptime
from datetime import date, time, datetime
from functools import partial

from simplejson import JSONEncoder, dumps, loads


def date_map(o):
    """

    >>> d = datetime(2001, 6, 1, 13, 14, 15)
    >>> date_map(d)
    {'__date__': (2001, 6, 1)}

    """
    return {'__date__':(o.year, o.month, o.day)}


def time_map(o):
    """

    >>> d = datetime(2001, 6, 1, 13, 14, 15)
    >>> time_map(d)
    {'__time__': (13, 14, 15, 0)}

    """
    return {'__time__':(o.hour, o.minute, o.second, o.microsecond)}


def datetime_map(o):
    """

    >>> d = datetime(2001, 6, 1, 13, 14, 15, 27550)
    >>> datetime_map(d)
    {'__datetime__': (2001, 6, 1, 13, 14, 15, 27550)}

    """
    return {'__datetime__':date_map(o)['__date__'] + time_map(o)['__time__']}


extra_encoders = [
    (datetime, datetime_map),
    (date, date_map),
    (time, time_map),
    ]


try:
    import dbi
except (ImportError, ):
    pass
else:
    def dbidate_map(o):
        # TODO:  convert str(o) to a datetime object, then
        # return datetime_map(obj).
        return strftime('%Y-%m-%d %H:%M:%S',
                        strptime(str(o), '%a %b %d %H:%M:%S %Y'))
    extra_encoders.append((type(dbi.dbiDate(0)), dbidate_map))

try:
    import pyodbc
except (ImportError, ):
    pass
else:
    def pyodbc_row_map(o):
        return list(o)
        #m = [(c[0], o[i]) for i, c in enumerate(o.cursor_description)]
        #return dict(m)
    extra_encoders.append((pyodbc.Row, pyodbc_row_map))

## we specify the python types we support for json encoding and
## decoding.  we do it here because it isn't available anywhere else,
## and because we can also specify extra types for library (and
## client) use.

supported_types = [
    bool, float, int, list, long, dict, tuple, type(None),
    ] + [e[0] for e in extra_encoders]
del(e)


class JSONHandler(JSONEncoder):
    """

    """
    def default(self, obj):
        for typ, call in extra_encoders:
            if isinstance(obj, typ):
                return call(obj)
        return JSONEncoder.default(self, obj)


def handle_decode(attrs):
    """

    >>> handle_decode({0:0, 1:1})
    {0: 0, 1: 1}

    """
    if '__datetime__' in attrs:
        return datetime(*attrs['__datetime__'])
    elif '__date__' in attrs:
        return date(*attrs['__date__'])
    elif '__time__' in attrs:
        return time(*attrs['__time__'])
    return attrs


json_dumps = partial(dumps, cls=JSONHandler)
json_loads = partial(loads, object_hook=handle_decode)
